/*****************************************************************************
* | File        :   SPI_RAM.cpp
* | Author      :   Waveshare Team
* | Function    :   23LC1024 DRIVER
* | Info        :
*----------------
* | This version:   V1.0
* | Date        :   2018-05-12
* | Info        :   Basic version
*
******************************************************************************/
#include "DEV_Config.h"
#include "SPI_RAM.h"
#include "Debug.h"
#include <rtthread.h>

/******************************************************************************
function:	Set the SPIRAM control mode
parameter:
See definition in SPIRAM.h, support byte mode, page mode, has any length mode
    BYTE_MODE
    PAGE_MODE
    STREAM_MODE
******************************************************************************/
void SPIRAM_Set_Mode(UBYTE mode)
{
    SPIRAM_CS_0;

    DEV_SPI_WriteByte(CMD_WRSR);
    DEV_SPI_WriteByte(mode);

    SPIRAM_CS_1;
}

/******************************************************************************
function:	Read one byte
parameter:
    Addr : An address
******************************************************************************/
UBYTE SPIRAM_RD_Byte(UDOUBLE Addr)
{
    UBYTE RD_Byte;

    SPIRAM_CS_0;
    DEV_SPI_WriteByte(CMD_READ);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    RD_Byte = DEV_SPI_ReadByte();
    SPIRAM_CS_1;

    return RD_Byte;
}

/******************************************************************************
function:	Write a char data at an address
parameter:
    Addr : address
    Data : Written data
******************************************************************************/
void SPIRAM_WR_Byte(UDOUBLE Addr,const UBYTE Data)
{
    // Write Addr,data
    SPIRAM_CS_0;

    DEV_SPI_WriteByte(CMD_WRITE);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    DEV_SPI_WriteByte(Data);
    SPIRAM_CS_1;
}

/******************************************************************************
function:	Starting from an address, reading a page of data
parameter:
    Addr : address
    pBuf : A pointer to cache a page of data
******************************************************************************/
void SPIRAM_RD_Page(UDOUBLE Addr, UBYTE *pBuf)
{
    UWORD i;

    // Write Addr, read data
    SPIRAM_CS_0;
    DEV_SPI_WriteByte(CMD_READ);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    for (i = 0; i < 32; i++) {
        *pBuf = DEV_SPI_ReadByte();
        pBuf++;
    }
    SPIRAM_CS_1;
}

/******************************************************************************
function:	Write a page of data starting from an address
parameter:
    Addr : address
    pBuf : A pointer to cache a page of data
******************************************************************************/
void SPIRAM_WR_Page(UDOUBLE Addr, UBYTE *pBuf)
{
    UWORD i;

    // Write Addr, read data
    SPIRAM_CS_0;
    DEV_SPI_WriteByte(CMD_WRITE);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    for (i = 0; i < 32; i++) {
        DEV_SPI_WriteByte(*pBuf);
        pBuf++;
    }
    SPIRAM_CS_1;
}

/******************************************************************************
function:	Read data of length Len from an address and store it in the buffer pointing to pBuf
parameter:
    Addr : address
    pBuf : A pointer to cache a page of data
    Len  : The length of data read
******************************************************************************/
void SPIRAM_RD_Stream(UDOUBLE Addr, UBYTE *pBuf, UDOUBLE Len)
{
    UWORD i;

    // Write Addr, read data
    SPIRAM_CS_0;
    DEV_SPI_WriteByte(CMD_READ);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    for (i = 0; i < Len; i++) {
        *pBuf = DEV_SPI_ReadByte();
        pBuf++;
    }
    SPIRAM_CS_1;
}

/******************************************************************************
function:	Writing fixed-length data from an address
parameter:
    Addr : address
    pBuf : A pointer to cache a page of data
    Len  : The length of data write
******************************************************************************/
void SPIRAM_WR_Stream(UDOUBLE Addr, UBYTE *pBuf, UDOUBLE Len)
{
    UWORD i;

    // Write Addr, read data
    SPIRAM_CS_0;
    DEV_SPI_WriteByte(CMD_WRITE);

    DEV_SPI_WriteByte((UBYTE)(Addr >> 16) & 0xff);
    DEV_SPI_WriteByte((UBYTE)(Addr >> 8) & 0xff);
    DEV_SPI_WriteByte((UBYTE)Addr & 0xff);

    for (i = 0; i < Len; i++) {
        DEV_SPI_WriteByte(*pBuf);
        pBuf++;
    }
    SPIRAM_CS_1;
}

#define printf rt_kprintf

void SPIRAM_Usertest(void)
{
    printf("23LC1024 SPI SRAM test start!\n");
    UWORD i = 0;
    UBYTE data = 0;
    UBYTE W_Data = 0, R_Data;
    SPIRAM_Set_Mode(BYTE_MODE);
    for (i = 0; i < 500; i++) {
        W_Data = data;
        SPIRAM_WR_Byte(i, data);
        R_Data = SPIRAM_RD_Byte(i);
        printf("addr = %d, W_Data = %d, R_Data = %d\n", i, W_Data, R_Data);
        data++;
    }
    printf("23LC1024 SPI SRAM test done!\n");
}
MSH_CMD_EXPORT_ALIAS(SPIRAM_Usertest, ram_test, "SPI RAM device test");

#if 1
static void ram_test_byte(void)
{
    UDOUBLE i = 0;
    UBYTE data = 0, temp = 0;
    uint32_t start_ms, end_ms;

    Debug("%s start!\n", __func__);
    SPIRAM_Set_Mode(BYTE_MODE);

    Debug("write start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i++) {
        SPIRAM_WR_Byte(i, data++);
    }
    end_ms = rt_tick_get_millisecond();
    Debug("write done, time cost: %dms!\n", end_ms - start_ms);


    data = 0;
    Debug("read start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i++) {
        temp = SPIRAM_RD_Byte(i);
        if (temp != data) {
            printf("read data mismatch wrote at %d: %d, %d\n", i, data, temp);
            break;
        }
        data++;
    }
    end_ms = rt_tick_get_millisecond();
    Debug("read done, time cost: %dms!\n", end_ms - start_ms);
    Debug("%s done!!\n", __func__);
}
MSH_CMD_EXPORT(ram_test_byte, SPI RAM device test by byte read/write);

void print_data(const char* name, UBYTE* addr, UWORD size)
{
    printf("%s: ", name);
    for (UWORD i = 0; i < size; i++) {
        printf("%02X ", addr[i]);
    }
    printf("\n");
}

void ram_test_page(void)
{
    UDOUBLE i = 0;
    UBYTE data[SPIRAM_PAGE_SIZE] = {0};
    UBYTE temp[SPIRAM_PAGE_SIZE];
    uint32_t start_ms, end_ms;

    Debug("%s start!\n", __func__);
    SPIRAM_Set_Mode(PAGE_MODE);
    for (i = 0; i < SPIRAM_PAGE_SIZE; i++) {
        data[i] = i & 0xFF;
        temp[i] = 0;
    }

    Debug("write start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i += SPIRAM_PAGE_SIZE) {
        SPIRAM_WR_Page(i, data);
    }
    end_ms = rt_tick_get_millisecond();
    Debug("write done, time cost: %dms!\n", end_ms - start_ms);

    Debug("read start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i += SPIRAM_PAGE_SIZE) {
        SPIRAM_RD_Page(i, temp);
        if (rt_memcmp(data, temp, SPIRAM_PAGE_SIZE) != 0) {
            printf("read data mismatch wrote at %d:\n", i, data, temp);
            print_data("data", data, SPIRAM_PAGE_SIZE);
            print_data("temp", temp, SPIRAM_PAGE_SIZE);
            break;
        }
    }
    end_ms = rt_tick_get_millisecond();
    Debug("read done, time cost: %dms!\n", end_ms - start_ms);
    Debug("%s done!!\n", __func__);
}
MSH_CMD_EXPORT(ram_test_page, SPI RAM device test by page read/write);

#define TEST_STREAM_SIZE 4096

void ram_test_stream(void)
{
    UDOUBLE i = 0;
    UBYTE* data = rt_malloc(TEST_STREAM_SIZE);
    UBYTE* temp = rt_malloc(TEST_STREAM_SIZE);
    uint32_t start_ms, end_ms;

    Debug("%s start!\n", __func__);
    SPIRAM_Set_Mode(STREAM_MODE);

    for (i = 0; i < TEST_STREAM_SIZE; i++) {
        data[i] = i & 0xFF;
        temp[i] = 0;
    }

    Debug("write start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i += TEST_STREAM_SIZE) {
        SPIRAM_WR_Stream(i, data, TEST_STREAM_SIZE);
    }
    end_ms = rt_tick_get_millisecond();
    Debug("write done, time cost: %dms!\n", end_ms - start_ms);

    Debug("read start...\n");
    start_ms = rt_tick_get_millisecond();
    for (i = 0; i < SPIRAM_SIZE; i += TEST_STREAM_SIZE) {
        rt_memset(temp, 0, TEST_STREAM_SIZE);
        SPIRAM_RD_Stream(i, temp, TEST_STREAM_SIZE);
        if (rt_memcmp(data, temp, TEST_STREAM_SIZE) != 0) {
            printf("read data mismatch wrote at %d:\n", i, data, temp);
            print_data("data", data, TEST_STREAM_SIZE);
            print_data("temp", temp, TEST_STREAM_SIZE);
            break;
        }
    }
    end_ms = rt_tick_get_millisecond();
    Debug("read done, time cost: %dms!\n", end_ms - start_ms);

    rt_free(temp);
    rt_free(data);
    Debug("%s done!!\n", __func__);
}
MSH_CMD_EXPORT(ram_test_stream, SPI RAM device test by stream read/write);
#endif
